package com.bowlong.bio3;

import static com.bowlong.bio3.B3Constants.b16;
import static com.bowlong.bio3.B3Constants.b8;
import static com.bowlong.bio3.B3Types.INT_0;
import static com.bowlong.bio3.B3Types.INT_1;
import static com.bowlong.bio3.B3Types.INT_10;
import static com.bowlong.bio3.B3Types.INT_11;
import static com.bowlong.bio3.B3Types.INT_12;
import static com.bowlong.bio3.B3Types.INT_13;
import static com.bowlong.bio3.B3Types.INT_14;
import static com.bowlong.bio3.B3Types.INT_15;
import static com.bowlong.bio3.B3Types.INT_16;
import static com.bowlong.bio3.B3Types.INT_16B;
import static com.bowlong.bio3.B3Types.INT_17;
import static com.bowlong.bio3.B3Types.INT_18;
import static com.bowlong.bio3.B3Types.INT_19;
import static com.bowlong.bio3.B3Types.INT_2;
import static com.bowlong.bio3.B3Types.INT_20;
import static com.bowlong.bio3.B3Types.INT_21;
import static com.bowlong.bio3.B3Types.INT_22;
import static com.bowlong.bio3.B3Types.INT_23;
import static com.bowlong.bio3.B3Types.INT_24;
import static com.bowlong.bio3.B3Types.INT_25;
import static com.bowlong.bio3.B3Types.INT_26;
import static com.bowlong.bio3.B3Types.INT_27;
import static com.bowlong.bio3.B3Types.INT_28;
import static com.bowlong.bio3.B3Types.INT_29;
import static com.bowlong.bio3.B3Types.INT_3;
import static com.bowlong.bio3.B3Types.INT_30;
import static com.bowlong.bio3.B3Types.INT_31;
import static com.bowlong.bio3.B3Types.INT_32;
import static com.bowlong.bio3.B3Types.INT_32B;
import static com.bowlong.bio3.B3Types.INT_4;
import static com.bowlong.bio3.B3Types.INT_5;
import static com.bowlong.bio3.B3Types.INT_6;
import static com.bowlong.bio3.B3Types.INT_7;
import static com.bowlong.bio3.B3Types.INT_8;
import static com.bowlong.bio3.B3Types.INT_8B;
import static com.bowlong.bio3.B3Types.INT_9;
import static com.bowlong.bio3.B3Types.INT_N1;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class B3Helper {
	static int read(InputStream in) throws IOException {
		return in.read();
	}

	static B3Types readTag(InputStream in) throws IOException {
		int ordinal = read(in);
		return B3Types.values()[ordinal];
	}

	static void write(OutputStream out, int b) throws IOException {
		out.write(b);
	}

	static void write(OutputStream out, B3Types type) throws IOException {
		int tag = type.ordinal();
		write(out, tag);
	}

	public static int readInt(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		switch (tag) {
		case INT_N1:
			return -1;
		case INT_0:
			return 0;
		case INT_1:
			return 1;
		case INT_2:
			return 2;
		case INT_3:
			return 3;
		case INT_4:
			return 4;
		case INT_5:
			return 5;
		case INT_6:
			return 6;
		case INT_7:
			return 7;
		case INT_8:
			return 8;
		case INT_9:
			return 9;
		case INT_10:
			return 10;
		case INT_11:
			return 11;
		case INT_12:
			return 12;
		case INT_13:
			return 13;
		case INT_14:
			return 14;
		case INT_15:
			return 15;
		case INT_16:
			return 16;
		case INT_17:
			return 17;
		case INT_18:
			return 18;
		case INT_19:
			return 19;
		case INT_20:
			return 20;
		case INT_21:
			return 21;
		case INT_22:
			return 22;
		case INT_23:
			return 23;
		case INT_24:
			return 24;
		case INT_25:
			return 25;
		case INT_26:
			return 26;
		case INT_27:
			return 27;
		case INT_28:
			return 28;
		case INT_29:
			return 29;
		case INT_30:
			return 30;
		case INT_31:
			return 31;
		case INT_32:
			return 32;
		case INT_8B: {
			int v = read(in);
			return v;
		}
		case INT_16B: {
			int value1 = read(in);
			int value2 = read(in);

			int v = (((value1 & 0xff) << 8) + ((value2 & 0xff) << 0));
			return v;
		}
		case INT_32B: {
			int value1 = read(in);
			int value2 = read(in);
			int value3 = read(in);
			int value4 = read(in);

			int v = ((value1 & 0xff) << 24) + ((value2 & 0xff) << 16)
					+ ((value3 & 0xff) << 8) + ((value4 & 0xff) << 0);
			return v;
		}
		default:
			throw new IOException("unknow type.");
		}
	}

	public static void writeInt(OutputStream out, int v) throws IOException {
		switch (v) {
		case -1:
			write(out, INT_N1);
			break;
		case 0:
			write(out, INT_0);
			break;
		case 1:
			write(out, INT_1);
			break;
		case 2:
			write(out, INT_2);
			break;
		case 3:
			write(out, INT_3);
			break;
		case 4:
			write(out, INT_4);
			break;
		case 5:
			write(out, INT_5);
			break;
		case 6:
			write(out, INT_6);
			break;
		case 7:
			write(out, INT_7);
			break;
		case 8:
			write(out, INT_8);
			break;
		case 9:
			write(out, INT_9);
			break;
		case 10:
			write(out, INT_10);
			break;
		case 11:
			write(out, INT_11);
			break;
		case 12:
			write(out, INT_12);
			break;
		case 13:
			write(out, INT_13);
			break;
		case 14:
			write(out, INT_14);
			break;
		case 15:
			write(out, INT_15);
			break;
		case 16:
			write(out, INT_16);
			break;
		case 17:
			write(out, INT_17);
			break;
		case 18:
			write(out, INT_18);
			break;
		case 19:
			write(out, INT_19);
			break;
		case 20:
			write(out, INT_20);
			break;
		case 21:
			write(out, INT_21);
			break;
		case 22:
			write(out, INT_22);
			break;
		case 23:
			write(out, INT_23);
			break;
		case 24:
			write(out, INT_24);
			break;
		case 25:
			write(out, INT_25);
			break;
		case 26:
			write(out, INT_26);
			break;
		case 27:
			write(out, INT_27);
			break;
		case 28:
			write(out, INT_28);
			break;
		case 29:
			write(out, INT_29);
			break;
		case 30:
			write(out, INT_30);
			break;
		case 31:
			write(out, INT_31);
			break;
		case 32:
			write(out, INT_32);
			break;
		default:
			if (b8(v)) {
				write(out, INT_8B);
				write(out, v);
			} else if (b16(v)) {
				write(out, INT_16B);
				write(out, (byte) ((v >> 8) & 0xff));
				write(out, (byte) ((v >> 0) & 0xff));
			} else {
				write(out, INT_32B);
				write(out, (byte) ((v >> 24) & 0xff));
				write(out, (byte) ((v >> 16) & 0xff));
				write(out, (byte) ((v >> 8) & 0xff));
				write(out, (byte) ((v >> 0) & 0xff));
			}
			break;
		}
	}

	public static byte[] toBytes(Map<Object, Object> map) throws IOException {
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
			toBytes(map, out);
			return out.toByteArray();
		}
	}

	public static void toBytes(Map<Object, Object> map, File f)
			throws IOException {
		try (FileOutputStream out = new FileOutputStream(f);) {
			toBytes(map, out);
		}
	}

	public static void toBytes(Map<Object, Object> map, OutputStream out)
			throws IOException {
		B3OutputStream b3out = new B3OutputStream(out);
		b3out.writeMap(map);
		out.flush();
	}

	public static byte[] toBytes(List<Object> list) throws IOException {
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
			toBytes(list, out);
			return out.toByteArray();
		}
	}

	public static void toBytes(List<Object> list, File f) throws IOException {
		try (FileOutputStream out = new FileOutputStream(f);) {
			toBytes(list, out);
		}
	}

	public static void toBytes(List<Object> list, OutputStream out)
			throws IOException {
		B3OutputStream b3out = new B3OutputStream(out);
		b3out.writeList(list);
		out.flush();
	}

	public static byte[] toBytes(Set<Object> set) throws IOException {
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
			toBytes(set, out);
			return out.toByteArray();
		}
	}

	public static void toBytes(Set<Object> set, File f) throws IOException {
		try (FileOutputStream out = new FileOutputStream(f)) {
			toBytes(set, out);
		}
	}

	public static void toBytes(Set<Object> set, OutputStream out)
			throws IOException {
		B3OutputStream b3out = new B3OutputStream(out);
		b3out.writeSet(set);
		out.flush();
	}

	public static Map<Object, Object> parseMap(byte[] b) throws IOException {
		try (ByteArrayInputStream in = new ByteArrayInputStream(b);) {
			return parseMap(in);
		}
	}

	public static Map<Object, Object> parseMap(File f) throws IOException {
		try (FileInputStream in = new FileInputStream(f)) {
			return parseMap(in);
		}
	}

	public static Map<Object, Object> parseMap(URL url) throws IOException {
		try (InputStream in = url.openStream()) {
			return parseMap(in);
		}
	}

	public static Map<Object, Object> parseMap(InputStream in)
			throws IOException {
		B3InputStream b3in = new B3InputStream(in);
		return b3in.readMap();
	}

	public static List<Object> parseList(byte[] b) throws IOException {
		try (ByteArrayInputStream in = new ByteArrayInputStream(b);) {
			return parseList(in);
		}
	}

	public static List<Object> parseList(File f) throws IOException {
		try (FileInputStream in = new FileInputStream(f)) {
			return parseList(in);
		}
	}

	public static List<Object> parseList(URL url) throws IOException {
		try (InputStream in = url.openStream()) {
			return parseList(in);
		}
	}

	public static List<Object> parseList(InputStream in) throws IOException {
		B3InputStream b3in = new B3InputStream(in);
		return b3in.readList();
	}

	public static Set<Object> parseSet(byte[] b) throws IOException {
		try (ByteArrayInputStream in = new ByteArrayInputStream(b);) {
			return parseSet(in);
		}
	}

	public static Set<Object> parseSet(File f) throws IOException {
		try (FileInputStream in = new FileInputStream(f)) {
			return parseSet(in);
		}
	}

	public static Set<Object> parseSet(URL url) throws IOException {
		try (InputStream in = url.openStream()) {
			return parseSet(in);
		}
	}

	public static Set<Object> parseSet(InputStream in) throws IOException {
		B3InputStream b3in = new B3InputStream(in);
		return b3in.readSet();
	}

}
